#include "BspGraphicsTest.hpp"


#include "Mesh.hpp"
#include "Visible.hpp"
#include "Octree.hpp"

#include <queue>
#include <boost/unordered_map.hpp>

class StaticVisible: public Visible {
public:
	StaticVisible( const AxisAlignedBox &box ): box_(box) {}

	const AxisAlignedBox& getBoundingBox() { return box_; }
	void resetBoundingBox( const AxisAlignedBox& box ) { box_=box; resink(); }

private:
	AxisAlignedBox box_;
};

class OctreeMesh;
typedef unsigned int FaceId;

class OctreeMeshSector: public StaticVisible {
public:
	OctreeMeshSector( OctreeMesh *mesh, const AxisAlignedBox &box );
	
	void addFace( FaceId face );
	void computeBoundingBox();

protected:
	void performRender();

private:
	OctreeMesh*         mesh_;
	std::vector<FaceId> faces_;
};

class OctreeMesh: public Mesh {
public:
	typedef OctreeMeshSector Sector;
	OctreeMesh( const MeshCreator &creator );

	void addToOctree( Octree *octree );

private:
	friend class OctreeMeshSector;
	void computeTriBox_( Poly &p, AxisAlignedBox &box );
	
	const vec3f& getVertex(FaceId fId, unsigned char vId) {
		return d_.verts[ d_.polys[fId].vertex[vId] ];
	}

	const vec3f& getNormal(FaceId fId, unsigned char vId) {
		return d_.verts[ d_.polys[fId].vertex[vId] ];
	}

	void render_( std::vector<FaceId>& faces );

	std::vector<OctreeMeshSector*> sectors_;
	std::queue<FaceId>             renderQueue_;
	std::vector<FrameId>           frameIds_;
};

OctreeMeshSector::OctreeMeshSector( OctreeMesh *mesh, const AxisAlignedBox &box )
: StaticVisible(box), mesh_(mesh) {}

void OctreeMeshSector::addFace( FaceId face ) {
	faces_.push_back(face);
}

void OctreeMeshSector::computeBoundingBox() {
	AxisAlignedBox box;
	for(unsigned i=1; i<faces_.size(); ++i) {
		for(int vid=0; vid<3; ++vid)
			box.merge( mesh_->getVertex( faces_[i], vid) );
	}
	
	resetBoundingBox(box);
}

void OctreeMeshSector::performRender() {
	mesh_->render_(faces_);
}

OctreeMesh::OctreeMesh( const MeshCreator &creator ): Mesh(creator) {
	frameIds_.resize(d_.polys.size(),0);
}

void OctreeMesh::computeTriBox_( Poly &p, AxisAlignedBox &box ) {
	box.setTwoCorners( d_.verts[p.vertex[0]], d_.verts[p.vertex[1]] );
	box.merge( d_.verts[p.vertex[2]] );
}

void OctreeMesh::addToOctree( Octree *octree) {
	boost::unordered_map<Octree*, Sector*> octants;

	// - create a sector for each octant there is a face in
	// - add all the faces in the octant to the sector
	AxisAlignedBox triBox;
	for(FaceId id=0; id<d_.polys.size(); ++id) {
		computeTriBox_(d_.polys[id],triBox);
		Octree* octant = octree->findOctant( triBox );
		auto ins = octants.insert( std::make_pair(octant, 0) );
		if( ins.second )
			ins.first->second = new Sector(this,triBox);
		
		ins.first->second->addFace( id );
	}
	sectors_.reserve( octants.size() );
	// - add the sectors to the octree
	// - TODO: Maybe I could use the octant info I have already.
	for(auto octsec = octants.begin(); octsec != octants.end(); ++octsec) {
		octsec->second->computeBoundingBox();
		octree->addVisible( octsec->second );
		sectors_.push_back( octsec->second );
	}
}

void OctreeMesh::render_( std::vector<FaceId>& faces ) {
	glBegin( GL_TRIANGLES );
	for(auto i=faces.begin(); i!=faces.end(); ++i)
		for(int v=0; v<3; ++v) {
			const vec3& vert = getVertex(*i,v);
			const vec3& norm = getNormal(*i,v);
			glNormal3f( norm.x,norm.y,norm.z );
			glVertex3f( vert.x,vert.y,vert.z );
		}
	glEnd();
}

